import * as React from "react";
import styles from "./style.less";
import Options, { OptionsProps, toClose } from "./Options";

export interface RenderItem {
  el: Element;
  paths: string[];
  isLeaf?: boolean;
  isEnd?: boolean;
}

export const toList = function render(
  root: Element,
  paths = [],
  items: RenderItem[] = []
) {
  let node = { el: root, paths, isLeaf: true };
  items.push(node);
  if (root.hasChildNodes()) {
    root.childNodes.forEach((el) => {
      if (el.nodeType === el.ELEMENT_NODE) {
        node.isLeaf = false;
        render(el as Element, paths.concat(el.nodeName), items);
      }
    });
    if (!node.isLeaf) {
      items.push({ ...node, isEnd: true });
    }
  }
  return items;
};
export const toString = (items: RenderItem[]) => {
  return items
    .map((item) => {
      let indent = " ".repeat(item.paths.length);
      let attrs = "";
      if (item.el.hasAttributes()) {
        attrs = [...item.el.attributes]
          .map((attr) => ` ${attr.name}="${attr.value}"`)
          .join("");
      }
      if (item.isLeaf) {
        return `${indent}<${item.el.tagName}${attrs}>${item.el.textContent}</${item.el.tagName}>`;
      }
      if (item.isEnd) {
        return `${indent}</${item.el.tagName}>`;
      } else {
        return `${indent}<${item.el.tagName}${attrs}>`;
      }
    })
    .join("\n");
};

export interface XMLBuilderProps {
  xml?: XMLDocument;
  onChange?: (xml: XMLDocument, text: string) => void;
}
export default (props: XMLBuilderProps) => {
  const { xml, onChange } = props;
  const [optionsProps, setOptionsProps] = React.useState<OptionsProps>();
  const [closed, updateClosed] = React.useState<Set<number>>(new Set());
  const refContainer = React.useRef<HTMLDivElement>()
  let items = toList(xml.documentElement);
  const closeTag = (index: number) => () => {
    const set = new Set(closed);
    set.add(index);
    updateClosed(set);
  };
  const openTag = (index: number) => () => {
    const set = new Set(closed);
    set.delete(index);
    updateClosed(set);
  };

  const showOption = (
    type: OptionsProps["type"],
    node: Element,
    attr?: Attr
  ) => (e: React.MouseEvent) => {
    e.preventDefault();
    if (refContainer.current) {
      const rect = refContainer.current.getBoundingClientRect()
      setOptionsProps({
        visible: true,
        type,
        node,
        attr,
        x: e.pageX - rect.left,
        y: e.pageY - rect.top,
        onFinish: () => onChange(xml, toString(toList(xml.documentElement))),
        onCancel: () => setOptionsProps(null)
      });
    }
  };

  React.useEffect(function () {
    const _close = toClose(function () {
      setOptionsProps(null);
    });
    document.addEventListener("click", _close);
    return function () {
      document.removeEventListener("click", _close);
    };
  }, []);

  let deepth = Number.MAX_VALUE;
  return (
    <div className={styles.container} ref={refContainer}>
      <ul className={styles["tag-hierarchy"]}>
        {items.map((item, index) => {
          let isClosed = false;
          let isHidden = false;
          if (item.paths.length > deepth) {
            isHidden = true;
          } else if (closed.has(index)) {
            isClosed = true;
            deepth = item.paths.length;
          } else {
            deepth = Number.MAX_VALUE;
          }

          return (
            <li
              key={index}
              style={{
                paddingLeft: item.paths.length * 16
              }}
              className={isHidden ? styles.hidden : ""}
            >
              {isClosed ? (
                <span className={styles["tag-open"]} onClick={openTag(index)}>
                  +
                </span>
              ) : (
                !item.isLeaf &&
                !item.isEnd && (
                  <span className={styles["tag-close"]} onClick={closeTag(index)}>
                    -
                  </span>
                )
              )}
              &lt;
              {item.isEnd && "/"}
              <span
                onContextMenu={showOption("tag-name", item.el)}
                className={styles["tag-name"]}
              >
                {item.el.nodeName}
              </span>
              {!item.isEnd &&
                item.el.hasAttributes() &&
                [...item.el.attributes].map((attr) => {
                  return (
                    <span
                      onContextMenu={showOption("tag-attr", item.el, attr)}
                      className={styles["tag-attr"]}
                      key={attr.name}
                    >
                      <span className={styles["tag-attr-name"]}>{attr.name}</span>="
                      <span className={styles["tag-attr-value"]}>{attr.value}</span>"
                    </span>
                  );
                })}
              &gt;
              {item.isLeaf && (
                <>
                  <span
                    onContextMenu={showOption("content", item.el)}
                    className={styles["tag-content"]}
                  >
                    {item.el.textContent}
                  </span>
                  &lt;/
                  <span
                    onContextMenu={showOption("tag-name", item.el)}
                    className={styles["tag-name"]}
                  >
                    {item.el.nodeName}
                  </span>
                  &gt;
                </>
              )}
              {isClosed && (
                <span className={styles["tag-ellipsis"]} onClick={openTag(index)}>
                  ...
                </span>
              )}
            </li>
          );
        })}
      </ul>
      {optionsProps && <Options {...optionsProps} />}
    </div>
  );
};
